﻿using System;
using System.Globalization;
using System.Windows;

namespace Gmantis.Controls.Chart.Extended
{
    public abstract class FunctionSeries : XYDataSeries
    {
        // Fields
        private static string js_proto = "function f(val){{  try{{ {0};}}  catch(e) {{ return e.message;}}}}f({1});";
        public static readonly DependencyProperty MaxProperty = DependencyProperty.Register("Max", typeof(double), typeof(FunctionSeries), new PropertyMetadata(1.0, new PropertyChangedCallback(FunctionSeries.OnDPChanged)));
        public static readonly DependencyProperty MinProperty = DependencyProperty.Register("Min", typeof(double), typeof(FunctionSeries), new PropertyMetadata(0.0, new PropertyChangedCallback(FunctionSeries.OnDPChanged)));
        public static readonly DependencyProperty SampleCountProperty = DependencyProperty.Register("SampleCount", typeof(int), typeof(FunctionSeries), new PropertyMetadata(100, new PropertyChangedCallback(FunctionSeries.OnDPChanged)));

        // Methods
        public FunctionSeries()
        {
            base.Display = SeriesDisplay.SkipNaN;
        }

        protected double CalculateValue(Func<double, double> func, string funcJS, double parameter, out string errmsg)
        {
            double naN = double.NaN;
            errmsg = "";
            if (this.ThrowException)
            {
                naN = this.CalculateValueInternal(func, funcJS, parameter, out errmsg);
            }
            else
            {
                try
                {
                    naN = this.CalculateValueInternal(func, funcJS, parameter, out errmsg);
                }
                catch (Exception exception)
                {
                    naN = double.NaN;
                    errmsg = exception.Message;
                }
            }
            return ValidateValue(naN);
        }

        private double CalculateValueInternal(Func<double, double> func, string funcJS, double parameter, out string errmsg)
        {
            errmsg = "";
            if (func != null)
            {
                return func(parameter);
            }
            if (!string.IsNullOrEmpty(funcJS))
            {
                object obj2 = Utils.EvalJS(CreateJS(funcJS, parameter));
                if (obj2 is double)
                {
                    return (double)obj2;
                }
                errmsg = obj2 as string;
            }
            return double.NaN;
        }

        private static string CreateJS(string funcJS, double parameter)
        {
            if (!funcJS.Contains("return"))
            {
                funcJS = "return " + funcJS;
            }
            return string.Format(CultureInfo.InvariantCulture, js_proto, new object[] { funcJS, parameter });
        }

        protected void FireException(string message)
        {
            if (this.ThrowException)
            {
                throw new Exception(message);
            }
        }

        protected string GetErrorString(string funcJS)
        {
            if (!string.IsNullOrEmpty(funcJS))
            {
                try
                {
                    return (Utils.EvalJS(CreateJS(funcJS, 0.0)) as string);
                }
                catch (Exception exception)
                {
                    return exception.Message;
                }
            }
            return "";
        }

        protected static void OnDPChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
        {
            ((FunctionSeries)obj).UpdateData();
        }

        public abstract void UpdateData();
        protected static double ValidateValue(double value)
        {
            if (double.IsInfinity(value))
            {
                value = double.NaN;
            }
            return value;
        }

        // Properties
        public double Max
        {
            get
            {
                return (double)base.GetValue(MaxProperty);
            }
            set
            {
                base.SetValue(MaxProperty, value);
            }
        }

        public double Min
        {
            get
            {
                return (double)base.GetValue(MinProperty);
            }
            set
            {
                base.SetValue(MinProperty, value);
            }
        }

        public int SampleCount
        {
            get
            {
                return (int)base.GetValue(SampleCountProperty);
            }
            set
            {
                base.SetValue(SampleCountProperty, value);
            }
        }

        public bool ThrowException { get; set; }
    }
}
